package com.mallcloud.mall.app.service.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.mallcloud.mall.app.api.constant.ApiConstants;
import com.mallcloud.mall.app.api.entity.dto.WxPay3RefundInfoDTO;
import com.mallcloud.mall.app.api.entity.dto.WxPay3UnifiedOrderInfoDTO;
import com.mallcloud.mall.app.api.entity.vo.*;
import com.mallcloud.mall.app.config.WechatPayV3Config;
import com.mallcloud.mall.app.service.AppOrderCouponRefundService;
import com.mallcloud.mall.app.service.AppOrderCouponService;
import com.mallcloud.mall.app.service.AppWxPay3Service;
import com.mallcloud.mall.app.utils.WxPayV3Request;
import com.mallcloud.mall.common.core.constant.enums.RLockEnum;
import com.mallcloud.mall.common.core.constant.enums.app.WxPayEnum;
import com.mallcloud.mall.common.core.constant.enums.mall.OrderEnum;
import com.mallcloud.mall.common.core.exception.FailException;
import com.mallcloud.mall.common.core.util.WebUtils;
import com.mallcloud.mall.mall.api.entity.MallOrderCouponModel;
import com.mallcloud.mall.mall.api.entity.MallOrderCouponRefundModel;
import com.mallcloud.mall.mall.service.MallOrderCouponService;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 微信支付
 */
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty("const.wxpay.v3.appId")
public class AppWxPay3ServiceImpl implements AppWxPay3Service {

	private final Sign wxPay3Sign;
	private final Verifier verifier;
	private final WxPayV3Request wxPayV3Request;
	private final Redisson redisson;
	private final WechatPayV3Config wechatPayV3Config;
	private final MallOrderCouponService orderCouponService;
	private final AppOrderCouponService appOrderCouponService;
	private final AppOrderCouponRefundService appOrderCouponRefundService;

	@SneakyThrows
	@Override
	public JSONObject payNotify(HttpServletRequest request, HttpServletResponse response) {
		RLock lock = redisson.getLock(RLockEnum.WXPAY3.getKeyFormat());
		//尝试加锁，最多等待5秒，上锁以后10秒自动解锁
		boolean tryLock = lock.tryLock(5, 10, TimeUnit.SECONDS);
		if (!tryLock) {
			throw new FailException("服务繁忙");
		}

		WxPay3NotifyDataVO wxPay3NotifyData = null;
		try {
			String aes = getWxPay3NotifyData(request);
			wxPay3NotifyData = JSONUtil.toBean(JSONUtil.parseObj(aes), WxPay3NotifyDataVO.class);
			if (!wxPay3NotifyData.isSuccess()) {
				log.info("未支付 trade_state={}", wxPay3NotifyData.getTradeState());
				return WechatPayV3Response.success(response);
			}

			//dosomething 处理业务
			//订单号
			String orderNo = wxPay3NotifyData.getOutTradeNo();
			MallOrderCouponModel orderCoupon = orderCouponService.getOne(new LambdaQueryWrapper<MallOrderCouponModel>()
					.eq(MallOrderCouponModel::getOrderNo, orderNo)
			);
			if (ObjectUtil.isNull(orderCoupon)) {
				log.error("[{}]订单数据丢失，需要手动补销售数据", orderNo);
				return WechatPayV3Response.fail(response, "订单数据丢失");
			}
			if (orderCoupon.getHasPayed()) {
				log.error("[{}]订单重复回调拦截", orderCoupon.getOrderNo());
				return WechatPayV3Response.success(response);
			}

			//支付完成时间
			Date successTime = DateUtil.parse(wxPay3NotifyData.getSuccessTime(), DatePattern.UTC_SIMPLE_PATTERN);
			orderCouponService.update(new LambdaUpdateWrapper<MallOrderCouponModel>()
					.eq(MallOrderCouponModel::getOrderNo, orderNo)
					.set(MallOrderCouponModel::getPayTime, successTime)
					.set(MallOrderCouponModel::getHasPayed, true)
			);
			orderCoupon.setHasPayed(true);
			orderCoupon.setPayTime(successTime);

			//处理业务
			appOrderCouponService.handleNotifyByWxPay(orderCoupon);
			return WechatPayV3Response.success(response);
		}
		catch (Exception e) {
			if (Objects.nonNull(wxPay3NotifyData)) {
				appOrderCouponService.updateTryFail(wxPay3NotifyData.getOutTradeNo(), OrderEnum.StatusEnum.FAIL.getStatus(), e.getMessage());
			}
			log.error("微信支付回调处理失败:" + e.getMessage(), e);
			return WechatPayV3Response.fail(response);
		}
		finally {
			lock.unlock();
			log.info("回调锁释放");
		}
	}

	@SneakyThrows
	@Override
	public JSONObject refundNotify(HttpServletRequest request, HttpServletResponse response) {
		RLock lock = redisson.getLock(RLockEnum.WXPAY3_REFUND.getKeyFormat());
		//尝试加锁，最多等待5秒，上锁以后10秒自动解锁
		boolean tryLock = lock.tryLock(5, 10, TimeUnit.SECONDS);
		if (!tryLock) {
			throw new FailException("服务繁忙");
		}

		WxPay3NotifyRefundDataVO wxPay3NotifyData = null;
		try {
			String aes = getWxPay3NotifyData(request);
			wxPay3NotifyData = JSONUtil.toBean(JSONUtil.parseObj(aes), WxPay3NotifyRefundDataVO.class);
			//dosomething 处理业务

			//订单号
			String orderNo = wxPay3NotifyData.getOutTradeNo();
			String refundStatus = wxPay3NotifyData.getRefundStatus();

			if (WxPayEnum.RefundStateEnum.ABNORMAL.name().equals(refundStatus)) {
				appOrderCouponService.updateTryFail(orderNo, OrderEnum.StatusEnum.FAIL.getStatus(), WxPayEnum.RefundStateEnum.ABNORMAL.getStatus());
				return WechatPayV3Response.success(response);
			}
			else if (WxPayEnum.RefundStateEnum.CLOSED.name().equals(refundStatus)) {
				appOrderCouponService.updateTryFail(orderNo, OrderEnum.StatusEnum.FAIL.getStatus(), WxPayEnum.RefundStateEnum.CLOSED.getStatus());
				return WechatPayV3Response.success(response);
			}
			else if (!WxPayEnum.RefundStateEnum.SUCCESS.name().equals(refundStatus)) {
				log.info("退款中 refund_status={}", refundStatus);
				return WechatPayV3Response.success(response);
			}
			MallOrderCouponRefundModel orderCouponRefund = appOrderCouponRefundService.getOne(new LambdaQueryWrapper<MallOrderCouponRefundModel>()
					.eq(MallOrderCouponRefundModel::getOrderNo, orderNo)
			);
			if (ObjectUtil.isNull(orderCouponRefund)) {
				log.error("[{}]退单数据丢失，需要重新发起退单", orderNo);
				return WechatPayV3Response.fail(response, "退单数据丢失");
			}
			//退单完成时间
			Date successTime = DateUtil.parse(wxPay3NotifyData.getSuccessTime(), DatePattern.UTC_SIMPLE_PATTERN);
			if (!orderCouponRefund.getHasRefunded()) {
				appOrderCouponRefundService.update(new LambdaUpdateWrapper<MallOrderCouponRefundModel>()
						.eq(MallOrderCouponRefundModel::getOrderNo, orderNo)
						.set(MallOrderCouponRefundModel::getRefundTime, successTime)
						.set(MallOrderCouponRefundModel::getHasRefunded, true)
				);
			}
			orderCouponRefund.setHasRefunded(true);
			orderCouponRefund.setRefundTime(successTime);

			//处理业务
			appOrderCouponRefundService.handleNotifyByWxRefund(orderCouponRefund);
			return WechatPayV3Response.success(response);
		}
		catch (Exception e) {
			if (Objects.nonNull(wxPay3NotifyData)) {
				appOrderCouponService.updateTryFail(wxPay3NotifyData.getOutTradeNo(), OrderEnum.StatusEnum.FAIL.getStatus(), e.getMessage());
			}
			log.error("微信支付回调处理失败:" + e.getMessage(), e);
			return WechatPayV3Response.fail(response);
		}
		finally {
			lock.unlock();
			log.info("回调锁释放");
		}
	}

	/**
	 * 解析并解密数据
	 */
	private String getWxPay3NotifyData(HttpServletRequest request) {
		try (BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()))) {

			String timeStamp = request.getHeader("Wechatpay-Timestamp");
			String nonce = request.getHeader("Wechatpay-Nonce");
			//微信传递过来的签名
			String signature = request.getHeader("Wechatpay-Signature");
			//证书序列号（微信平台）
			String serialNo = request.getHeader("Wechatpay-Serial");
			log.debug("v3回调数据 timeStamp: {}", timeStamp);
			log.debug("v3回调数据 signature: {}", signature);
			log.debug("v3回调数据 nonce: {}", nonce);
			log.debug("v3回调数据 serialNo: {}", serialNo);

			if (!checkSerialNumber(serialNo)) {
				throw new FailException("微信支付平台证书的序列号一致");
			}

			StringBuilder stringBuilder = new StringBuilder();
			String line;
			while ((line = reader.readLine()) != null) {
				stringBuilder.append(line);
			}
			String body = stringBuilder.toString();
			log.debug("v3回调数据 resData: {}", body);

			if (!verify(timeStamp, nonce, signature, serialNo, body)) {
				throw new FailException("微信验签不通过");
			}

			WxPay3NotifyVO wxPayV3 = JSONUtil.toBean(JSONUtil.parseObj(body), WxPay3NotifyVO.class);
			if (!wxPayV3.isSuccess()) {
				throw new FailException("event_type!=TRANSACTION.SUCCESS || resource_type!=encrypt-resource 导致失败");
			}
			WxPay3NotifyVO.Resource resource = wxPayV3.getResource();

			// 解密工具类
			AesUtil aesUtil = new AesUtil(wechatPayV3Config.getApiKey().getBytes(StandardCharsets.UTF_8));
			String aes = aesUtil.decryptToString(
					resource.getAssociatedData().getBytes(StandardCharsets.UTF_8),
					resource.getNonce().getBytes(StandardCharsets.UTF_8),
					resource.getCipherText()
			);
			log.debug("解密后={}", aes);
			return aes;
		}
		catch (Exception e) {
			log.error("微信支付回调失败", e);
			throw new FailException("微信支付回调失败", e);
		}
	}

	private boolean checkSerialNumber(String serialNo) {
		String sn = verifier.getValidCertificate().getSerialNumber().toString(16);
		log.info("verifier.getSerialNumber={}", sn);
		return serialNo.equalsIgnoreCase(sn);
	}

	private boolean verify(String timeStamp, String nonce, String signature, String serialNo, String body) {
		String verifySignature = Stream.of(timeStamp, nonce, body).collect(Collectors.joining("\n", "", "\n"));
		return verifier.verify(
				serialNo,
				verifySignature.getBytes(StandardCharsets.UTF_8),
				signature
		);

	}

	//--退款---------------------------------------------------
	@SneakyThrows
	@Override
	public void refund(WxPay3RefundInfoDTO refundInfo) {
		//加锁
		refundInfo.setNotifyUrl(wechatPayV3Config.getRefundNotifyUrl());
		Object paramsStr = JSON.toJSON(refundInfo);
		log.info("请求参数 ===> {}", paramsStr);
		String resStr = wxPayV3Request.wechatHttpPost(ApiConstants.WxPayApi.DOMESTIC_REFUNDS.getUrl(), paramsStr.toString());
		log.info("请求返回 ===> {}", resStr);
		Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
		});
		//SUCCESS：退款成功
		//CLOSED：退款关闭
		//PROCESSING：退款处理中
		//ABNORMAL：退款异常
		if ("ABNORMAL".equals(resMap.get("status"))) {
			log.error("退款异常,可前往【商户平台—>交易中心】，手动处理此笔退款");
			return;
		}
		else if ("CLOSED".equals(resMap.get("status"))) {
			log.error("退款关闭");
			return;
		}
		else if (!"SUCCESS".equals(resMap.get("status"))) {
			log.info("退款中 status={}", resMap.get("status"));
			return;
		}
		Date successTime = DateUtil.parse(resMap.get("success_time").toString(), DatePattern.UTC_SIMPLE_PATTERN);
		appOrderCouponRefundService.refundSuccess(refundInfo.getOutTradeNo(), successTime);
	}

	//--统一下单---------------------------------------------------
	@SneakyThrows
	@Override
	public WxPay3UnifiedOrderResponseVO unifiedOrder(WxPay3UnifiedOrderInfoDTO unifiedOrderInfo) {
		//交易类型
		String tradeType = unifiedOrderInfo.getTradeType();
		// 拓展属性
		extend(unifiedOrderInfo);

		// 重写type值，因为小程序会多一个下划线(sub_type)
		String[] split = tradeType.split(StrUtil.UNDERLINE);
		String newType = split[split.length - 1].toLowerCase();

		String requestParams = JSON.toJSONString(unifiedOrderInfo);
		log.info("统一下单-->请求参数 ===> {}", requestParams);
		String responseStr = wxPayV3Request.wechatHttpPost(wechatPayV3Config.getBaseUrl().concat(ApiConstants.WxPayApi.UNIFIED_ORDER_V3.getUrl().concat(newType)), requestParams);
		log.info("统一下单-->返回 ===> {}", responseStr);

		WxPay3UnifiedOrderResponseVO responseVO = JSONObject.toJavaObject(JSONObject.parseObject(responseStr), WxPay3UnifiedOrderResponseVO.class);
		responseVO.setType(tradeType);

		// 设置签名信息,Native与H5不需要
		if (WxPayEnum.TradeTypeEnum.H5.name().equalsIgnoreCase(tradeType) ||
				WxPayEnum.TradeTypeEnum.NATIVE.name().equalsIgnoreCase(tradeType)) {
			responseVO.setSignMap(null);
		}
		// JSAPI、SUB_JSAPI(小程序)
		if (WxPayEnum.TradeTypeEnum.JSAPI.name().equalsIgnoreCase(tradeType) ||
				WxPayEnum.TradeTypeEnum.SUB_JSAPI.name().equalsIgnoreCase(tradeType)) {
			responseVO.setSignMap(paySignJsApi(responseVO));
		}
		// APP
		else if (WxPayEnum.TradeTypeEnum.APP.name().equalsIgnoreCase(tradeType)) {
			responseVO.setSignMap(paySignApp(responseVO));
		}
		return responseVO;
	}

	/**
	 * app唤起支付参数
	 */
	private WxPay3UnifiedOrderResponseVO.SignObj paySignApp(WxPay3UnifiedOrderResponseVO responseVO) {
		//预支付交易会话标识
		String prepayId = responseVO.getPrepayId();

		WxPay3UnifiedOrderResponseVO.SignApp signObj = new WxPay3UnifiedOrderResponseVO.SignApp();

		long timeMillis = System.currentTimeMillis();
		String timeStamp = String.valueOf(timeMillis / 1000);
		String nonceStr = String.valueOf(timeMillis);
		String appId = wechatPayV3Config.getAppId();
		String packageStr = "Sign=WXPay";

		// 设置公共字段
		paySignCommon(signObj, timeStamp, nonceStr, appId, packageStr);

		signObj.setPrepayId(prepayId);
		// 使用字段appId、timeStamp、nonceStr、prepayId进行签名
		signObj.setPartnerId(wechatPayV3Config.getMchId());
		signObj.setSign(Base64.encode(wxPay3Sign.sign(appId + "\n" + timeStamp + "\n" + nonceStr + "\n" + packageStr + "\n")));
		return signObj;
	}

	/**
	 * JSAPI唤起支付参数
	 */
	private WxPay3UnifiedOrderResponseVO.SignJsApi paySignJsApi(WxPay3UnifiedOrderResponseVO responseVO) {
		//预支付交易会话标识
		String prepayId = responseVO.getPrepayId();

		WxPay3UnifiedOrderResponseVO.SignJsApi signObj = new WxPay3UnifiedOrderResponseVO.SignJsApi();

		long timeMillis = System.currentTimeMillis();
		String timeStamp = String.valueOf(timeMillis / 1000);
		String nonceStr = String.valueOf(timeMillis);
		String appId = wechatPayV3Config.getAppId();
		String packageStr = String.format("prepay_id=%s", prepayId);

		// 设置公共字段
		paySignCommon(signObj, timeStamp, nonceStr, appId, packageStr);

		// 使用字段appId、timeStamp、nonceStr、package进行签名
		signObj.setPaySign(Base64.encode(wxPay3Sign.sign(appId + "\n" + timeStamp + "\n" + nonceStr + "\n" + packageStr + "\n")));
		return signObj;
	}

	private void paySignCommon(WxPay3UnifiedOrderResponseVO.SignObj signObj, String timeStamp, String nonceStr, String appId, String packageStr) {
		signObj.setNonceStr(nonceStr);
		signObj.setTimeStamp(timeStamp);
		signObj.setAppId(appId);
		signObj.setSignType("RSA");
		signObj.setPackageStr(packageStr);
	}

	/**
	 * 统一下单参数拓展
	 */
	@SneakyThrows
	private void extend(WxPay3UnifiedOrderInfoDTO unifiedOrderInfo) {
		unifiedOrderInfo.setAppId(wechatPayV3Config.getAppId());
		unifiedOrderInfo.setMchId(wechatPayV3Config.getMchId());
		unifiedOrderInfo.setNotifyUrl(wechatPayV3Config.getNotifyUrl());

		WxPay3UnifiedOrderInfoDTO.Amount amount = new WxPay3UnifiedOrderInfoDTO.Amount();
		amount.setTotal(new BigDecimal(unifiedOrderInfo.getTotalFee()).multiply(new BigDecimal(100)).longValue());
		unifiedOrderInfo.setAmount(amount);

		if (unifiedOrderInfo.getTradeType().equalsIgnoreCase(WxPayEnum.TradeTypeEnum.JSAPI.name()) ||
				unifiedOrderInfo.getTradeType().equalsIgnoreCase(WxPayEnum.TradeTypeEnum.SUB_JSAPI.name())) {
			WxPay3UnifiedOrderInfoDTO.Payer payer = new WxPay3UnifiedOrderInfoDTO.Payer();
			payer.setOpenId(unifiedOrderInfo.getOpenId());
			unifiedOrderInfo.setPayer(payer);
		}

		WxPay3UnifiedOrderInfoDTO.SceneInfo sceneInfo = new WxPay3UnifiedOrderInfoDTO.SceneInfo();
		sceneInfo.setPayerClientIp(WebUtils.getIP());
		unifiedOrderInfo.setSceneInfo(sceneInfo);
	}
}
